package interpreter.debugger;

import java.util.Set;

/**
 *
 * @author Kris Selbekk
 */
public class FunctionEnvironmentRecord {
    private Table table;
    private String functionName;
    private int lineStart, lineEnd, lineCurrent;

    // Constructors
    public FunctionEnvironmentRecord() {
        table = new Table();
    }

    public static void main(String[] args) {
        // The test case is hard coded, as instructed
        String testcase = "BS\n"+
                "Function g 1 20\n"+
                "Line 5\n"+
                "Enter a 4\n"+
                "Enter b 2\n"+
                "Enter c 7\n"+
                "Enter a 1\n"+
                "Pop 2\n"+
                "Pop 1\n";

       // Creates the FunctionEnvironmentRecord
       FunctionEnvironmentRecord fctEnvRecord = new FunctionEnvironmentRecord();

       fctEnvRecord.process( testcase );
    }

    /* Processing Method */
    public void process( String input ) {
        String[] lines = input.split("\\n");
        for( int i = 0; i<lines.length; i++) {
            String[] tokens = lines[i].split("\\s");

            // Calls the correct helper method
            if( tokens[0].equals("BS") )
                processBS();
            else if( tokens[0].equals("Function"))
                processFunc(tokens[1], Integer.parseInt(tokens[2]), Integer.parseInt(tokens[3]));
            else if( tokens[0].equals("Line") )
                processLine( Integer.parseInt(tokens[1]) );
            else if( tokens[0].equals("Enter") ) {
                if( tokens.length == 3 ) processEnter( tokens[1], Integer.parseInt(tokens[2]) );
                else processEnter( null, Integer.parseInt(tokens[1]) );
            }
            else if( tokens[0].equals("Pop") )
                processPop( Integer.parseInt(tokens[1]) );

            // Prints out the command used + the environment dump
            // System.out.print( lines[i] + " ");
            // printDump();
        }
    }

    /* Bytecode implementations */
    private void processBS() { table.beginScope(); }
    private void processFunc(String name, int start, int end) {
        setFunctionName(name); setLineStart(start); setLineEnd(end); setLineCurrent(0);
    }
    private void processLine(int lineNum) { setLineCurrent(lineNum); }
    private void processEnter(String var, int val) { table.put( var, val ); }
    private void processPop(int num) { table.endScope(num); }
    
    /* Dump Methods */
    public void printDump() { System.out.println(makeDump()); }
    public String getDump() { return makeDump(); }
    private String makeDump() {
        String out = "(";
        // Adding the environment record
        out += "<"+table.toString()+">,";
        // Adding the function name, or dash if not set
        if( functionName != null ) out += functionName+",";
        else out += "-,";
        // Adding function start line
        if( lineStart > 0 ) out += lineStart+",";
        else out += "-,";
        // Adding function end line
        if( lineEnd > 0 ) out += lineEnd+",";
        else out += "-,";
        // Adding function current line
        if( lineCurrent > 0 ) out += lineCurrent;
        else out += "-";
        
        out += ")";
        return out;
    }

    public String getArguments() {
        StringBuilder args = new StringBuilder();

        Set<String> symbols = table.keys();

        for( String symbol : symbols ) { // What if overwritten? BUG
            args.append(symbol);
            args.append(" = ");
            args.append( table.get(symbol) );
            args.append("\n");
        }

        return args.toString();
    }

    /* Setters and Getters */
    public String getFunctionName() { return functionName; }
    public void setFunctionName(String s) { functionName = s; }

    public int getLineStart() { return lineStart; }
    public void setLineStart( int i ) { lineStart = i; }

    public int getLineEnd() { return lineEnd; }
    public void setLineEnd( int i ) { lineEnd = i; }

    public int getLineCurrent() { return lineCurrent; }
    public void setLineCurrent( int i ) { lineCurrent = i; }

    public int formalCount() { return table.keys().size(); } // hack

}

/* Code adapted from constrainer.Table.java */

class Binder {
  private Object value;
  private String prevtop;   // prior symbol in same scope
  private Binder tail;      // prior binder for same symbol
                            // restore this when closing scope
  Binder(Object v, String p, Binder t) {
	value=v; prevtop=p; tail=t;
  }

  Object getValue() { return value; }
  String getPrevtop() { return prevtop; }
  Binder getTail() { return tail; }
}


/** <pre>
 * The Table class is similar to java.util.Dictionary, except that
 * each key must be a String and there is a scope mechanism.
 *
 * Consider the following sequence of events for table t:
 * t.put(Symbol("a"),5)
 * t.beginScope()
 * t.put(Symbol("b"),7)
 * t.put(Symbol("a"),9)
 *
 * symbols will have the key/value pairs for Symbols "a" and "b" as:
 *
 * Symbol("a") ->
 *     Binder(9, Symbol("b") , Binder(5, null, null) )
 * (the second field has a reference to the prior Symbol added in this
 * scope; the third field refers to the Binder for the Symbol("a")
 * included in the prior scope)
 * Binder has 2 linked lists - the second field contains list of symbols
 * added to the current scope; the third field contains the list of
 * Binders for the Symbols with the same string id - in this case, "a"
 *
 * Symbol("b") ->
 *     Binder(7, null, null)
 * (the second field is null since there are no other symbols to link
 * in this scope; the third field is null since there is no Symbol("b")
 * in prior scopes)
 *
 * top has a reference to Symbol("a") which was the last symbol added
 * to current scope
 *
 * Note: What happens if a symbol is defined twice in the same scope??
 * </pre>
*/
class Table {

  private java.util.HashMap<String,Binder> symbols = new java.util.HashMap<String,Binder>();
  private String top;    // reference to last symbol added to
                         // current scope; this essentially is the
                         // start of a linked list of symbols in scope

  public Table(){}


 /**
  * Gets the object associated with the specified symbol in the Table.
  */
  public Object get(String key) {
	Binder e = symbols.get(key);
	return e.getValue();
  }

 /**
  * Puts the specified value into the Table, bound to the specified Symbol.<br>
  * Maintain the list of symbols in the current scope (top);<br>
  * Add to list of symbols in prior scope with the same string identifier
  */
  public void put(String key, Object value) {
	symbols.put(key, new Binder(value, top, symbols.get(key)));
	top = key;
  }

 /**
  * Remembers the current state of the Table; push new mark on mark stack
  */
  public void beginScope() {
    top=null;
  }

 /**
  * Restores the table to what it was at the most recent beginScope
  *	that has not already been ended.
  */
  public void endScope( int n ) {
      for( int i=0; i<n; i++) {
	   Binder e = symbols.get(top);
	   if (e.getTail()!=null) symbols.put(top,e.getTail());
	   else symbols.remove(top);
	   top = e.getPrevtop();
      }
  }

  public String toString() {
      String out = "";
      for( String s : keys() ) {
          out += s + "/" + get(s) + ",";          
      }
      if( out.length() > 0) out = out.substring(0, out.length()-1);

      return out;
  }
  /**
   * @return a set of the Table's symbols.
   */
  public java.util.Set<String> keys() {return symbols.keySet();}
}